package com.diagrammer.common
{
	import flash.geom.Point;
	import flash.geom.Rectangle;

	/**
	 * 无状态辅助类，做一些运算操作，单例
	 * 
	 * @author Dwei <b>2011-11-22</b>
	 * 
	 */	
	public class Algorithm
	{
//============================================================================
//constructor
//============================================================================
		public function Algorithm(inner:AssistantInner)
		{
		}
//============================================================================
//static
//============================================================================
		private static var instance:Algorithm;
		
		/**
		 * 获取一个Assistant的实例对象
		 * @return 
		 * 
		 */		
		public static function getInstance():Algorithm
		{
			if (!instance) instance = new Algorithm(new AssistantInner());
			return instance;
		}
//============================================================================
//public
//============================================================================
		/**
		 * 计算 点target到矩形border中心点的连线与border边框的交点</br>
		 * 如果矩形包含点target（包括点在矩形内部和在边上）
		 * @param target
		 * @param border
		 * 
		 */		
		public function getCrossPointWithRectangle(target:Point, border:Rectangle):Point
		{
			resetRectangle(border);
			//矩形中心点
			var cPoint:Point = new Point(border.x + border.width / 2, border.y + border.height / 2);
			//如果传入数据为null，或者传入矩形无面积，或者点 target 在节点内部，直接返回矩形中心点
			if (border.isEmpty() || border.containsPoint(target))
				return cPoint;
			//如果点 target 与点 cPoint 连线为垂直线
			if (target.x == cPoint.x)
				return new Point(cPoint.x, target.y > cPoint.y?border.bottom:border.y);
			//如果点 target 与点 cPoint 连线为水平线
			if (target.y == cPoint.y)
				return new Point(target.x > cPoint.x?border.right:border.x, cPoint.y);
			
			//节点左上右下对角线与水平线夹角
			var angle:Number = Math.atan2(border.height, border.width);
			//点 target 与点 cPoint 连线水平距离
			var xLength:Number = target.x - cPoint.x;
			//点 target 与点 cPoint 连线垂直距离
			var yLength:Number = target.y - cPoint.y;
			//点 target 与点 cPoint 连线与水平线夹角
			var angleP2P:Number = Math.atan2(yLength, xLength);
			//角 angleP2P 伪正切值（即 tan(a) = tan(Math.PI - a)）
			var tangent:Number = yLength / Math.abs(xLength);
			//角 angleP2P 伪余切值（即 cot(a) = cot(-a)）
			var cotangent:Number = xLength / Math.abs(yLength);
			
			if (Math.abs(angleP2P) >= angle && Math.abs(angleP2P) <= Math.PI - angle)//与上下边相交（包括顶点）
				return new Point(cPoint.x + cotangent * border.height / 2, angleP2P < 0?border.y:border.bottom);
			else//与左右边相交
				return new Point(Math.abs(angleP2P) < angle?border.right:border.x, cPoint.y + tangent * border.width / 2);
		}
		
		/**
		 * 以点 center 为中心点，点 target 旋转 angle 弧度(会创建一个新的 Point 对象，不会修改 oPoint 数据)
		 * @param target 要旋转的点，如果传入 null，返回 null
		 * @param center 旋转中心
		 * @param angle 旋转弧度（弧度制）
		 * @return 旋转后得到的新点
		 * 
		 */		
		public function rotatePoint(target:Point, center:Point, angle:Number):Point
		{
			var sin:Number = Math.sin(angle);
			var cos:Number = Math.cos(angle);
			var targetClone:Point = new Point(target.x - center.x, target.y - center.y);
			return new Point(cos * targetClone.x - sin * targetClone.y + center.x, cos * targetClone.y + sin * targetClone.x + center.y);
		}
		
		/**
		 * 给定三个点p1,p2,p3，计算所形成的凸三角形在点p1处的角度:[0,Math.PI]
		 * @param p1
		 * @param p2
		 * @param p3
		 * @return 
		 * 
		 */		
		public function getAngleInTriangle(p1:Point, p2:Point, p3:Point):Number
		{
			if (p1.equals(p2) || p2.equals(p3) || p3.equals(p1)) return 0;
			var angleP21:Number = Math.atan2(p2.y - p1.y, p2.x - p1.x);
			var angleP31:Number = Math.atan2(p3.y - p1.y, p3.x - p1.x);
			var angle:Number = Math.abs(angleP31 - angleP21);
			if (angle > Math.PI) angle = Math.PI * 2 - angle;
			return angle;
		}
		
		/**
		 * 在由点p1和p2确定的直线上求一点，使得该点到p1的距离为legnth
		 * @param p1
		 * @param p2
		 * @param length 目标点与p1点的距离(-,+)
		 * @return 
		 * 
		 */		
		public function getPointOnLine(p1:Point, p2:Point, length:Number):Point
		{
			var ratio:Number = length / getDistanceTwoPoint(p1, p2);
			return new Point(ratio * (p2.x - p1.x) + p1.x, ratio * (p2.y - p1.y) + p1.y);
		}
		
		/**
		 * 求一点，使得其与点p1的连线与x轴正方向夹角为angle弧度，并且所求得的点与p1距离为length
		 * @param p1
		 * @param angle 弧度
		 * @param length 目标点与p1点的距离(-,+)
		 * @return 
		 * 
		 */		
		public function getPointOnAngle(p1:Point, angle:Number, length:Number):Point
		{
			return new Point(length * Math.cos(angle) + p1.x, length * Math.sin(angle) + p1.y);
		}
		
		/**
		 * 点p在border内（不包括在边上），获取线段p->target与矩形border的交点
		 * @param target
		 * @param p
		 * @param border
		 * @return 如果未找到交点，返回null
		 * 
		 */		
		public function getCrossPointLineRectangle(target:Point, p:Point, border:Rectangle):Point
		{
			resetRectangle(border);
			if (border.isEmpty()) return null;
			var topRight:Point = new Point(border.right, border.top);
			var bottomLeft:Point = new Point(border.left, border.bottom);
			if (p.x <= border.left || p.x >= border.right || p.y <= border.top || p.y >= border.bottom) return null;
			var tCross:Point = getCrossPointWithTwoLines(border.topLeft, topRight, target, p, true);
			var lCross:Point = getCrossPointWithTwoLines(border.topLeft, bottomLeft, target, p, true);
			var rCross:Point = getCrossPointWithTwoLines(border.bottomRight, topRight, target, p, true);
			var bCross:Point = getCrossPointWithTwoLines(border.bottomRight, bottomLeft, target, p, true);
			var crosses:Array = [];
			if (tCross && border.containsPoint(tCross)) crosses.push(tCross);
			if (lCross && border.containsPoint(lCross)) crosses.push(lCross);
			if (rCross && border.containsPoint(rCross)) crosses.push(rCross);
			if (bCross && border.containsPoint(bCross)) crosses.push(bCross);
			var pLoop:Point;
			var effective:Array = [];
			for each (pLoop in crosses)
			{
				if (border.containsPoint(pLoop)) effective.push(pLoop);
			}
			if (effective.length == 1) return effective[0];
			return null;
		}
		
		/**
		 * 获取由点p1A和p1B确定的线条p1与由点p2A和p2B确定的线条p2的交点
		 * @param p1A
		 * @param p1B
		 * @param p2A
		 * @param p2B
		 * @param segment 线条p1A->p1B和p2A->p2B是否是线段
		 * @return 如果无交点或者p1与p2重复，返回null
		 * 
		 */		
		public function getCrossPointWithTwoLines(p1A:Point, p1B:Point, p2A:Point, p2B:Point, segment:Boolean = false):Point
		{
			var angle:Number;
			if (p1A.equals(p1B))
			{
				if (p2A.equals(p2B)) return p1A.equals(p2A)?p1A:null;
				angle = getAngleInTriangle(p1A, p2A, p2B);
				return (angle == 0 || angle == Math.PI)?p1A:null;
			}
			else if  (p2A.equals(p2B))
			{
				if (p1A.equals(p1B)) return p2A.equals(p1A)?p2A:null;
				angle = getAngleInTriangle(p2A, p1A, p1B);
				return (angle == 0 || angle == Math.PI)?p2A:null;
			}
			angle = Math.atan2(p1B.y - p1A.y, p1B.x - p1A.x);
			var p2ACopy:Point = p2A.clone();
			var p2BCopy:Point = p2B.clone();
			p2A = rotatePoint(p2A, p1A, -angle);
			p2B = rotatePoint(p2B, p1A, -angle);
			//平行线，返回null
			if (p2A.y == p2B.y) return null;
			//垂直相交
			var result:Point;
			if (p2A.x == p2B.x)
				result = rotatePoint(new Point(p2A.x, p1A.y), p1A, angle);
			else
			{
				var ratio:Number = (p2B.y - p2A.y) / (p2B.x - p2A.x);
				var crossPoint:Point = new Point(p2A.x + ((p1A.y - p2A.y) / ratio), p1A.y);
				result = rotatePoint(crossPoint, p1A, angle);
			}
			return segment?((isPointOnLine(result, p1A, p1B) && isPointOnLine(result, p2ACopy, p2BCopy))?result:null):result;
		}
		
		/**
		 * 获取两个点的距离
		 * @param p1
		 * @param p2
		 * @return 
		 * 
		 */		
		public function getDistanceTwoPoint(p1:Point, p2:Point):Number
		{
			if (p1.equals(p2)) return 0;
			return Math.sqrt((p2.y - p1.y) * (p2.y - p1.y) + (p2.x - p1.x) * (p2.x - p1.x));
		}
		
		/**
		 * 获取点target到由点p1和点p2确定的直线的距离
		 * @param target
		 * @param p1
		 * @param p2
		 * @return 
		 * 
		 */		
		public function getDistancePointToLine(target:Point, p1:Point, p2:Point):Number
		{
			if (p1.equals(p2)) return getDistanceTwoPoint(target, p1);
			var angle:Number = Math.atan2(p2.y - p1.y, p2.x - p1.x);
			var pNew:Point = rotatePoint(target, p1, -angle);
			return Math.abs(pNew.y - p1.y);
		}
		
		/**
		 * 判断点target是否在线段p1->p2上
		 * @param target
		 * @param p1
		 * @param p2
		 * @return 
		 * 
		 */		
		public function isPointOnLine(target:Point, p1:Point, p2:Point):Boolean
		{
			if (p1.equals(p2)) return target.equals(p1);
			var length12:Number = getDistanceTwoPoint(p1, p2);
			var length1:Number = getDistanceTwoPoint(target, p1);
			var length2:Number = getDistanceTwoPoint(target, p2);
			return Math.round(length1 + length2) == Math.round(length12);
		}
		
		/**
		 * 获取线条(矩形target中心点与矩形source中心点)与矩形source的交点
		 * @param source 源矩形
		 * @param target 目标矩形
		 * @param isOffset 线条(矩形target中心点与矩形source中心点)是否需要偏移
		 * @param offset 线条(矩形target中心点与矩形source中心点)的偏移值
		 * @return 如果矩形source和target相交，返回null
		 * 
		 */		
		public function getCrossPointRectangles(
			source:Rectangle, 
			target:Rectangle, 
			isOffset:Boolean = false, 
			offset:Number = 5):Point
		{
			if (source.intersects(target)) return null;
			if (source.isEmpty()) return source.topLeft;
			if (target.isEmpty()) return getCrossPointWithRectangle(target.topLeft, source);
			
			var cTPoint:Point = new Point(target.x + target.width / 2, target.y + target.height / 2);
			if (!isOffset || offset == 0) return getCrossPointWithRectangle(cTPoint, source);
			
			var cFPoint:Point = new Point(source.x + source.width / 2, source.y + source.height / 2);
			var tempPoint:Point = getCrossPointWithRectangle(cTPoint, source);
			var length:Number = getDistanceTwoPoint(tempPoint, cFPoint);
			tempPoint = rotatePoint(cTPoint, cFPoint, offset / length);
			return getCrossPointWithRectangle(tempPoint, source);
		}
//============================================================================
//private
//============================================================================
		/**
		 * 使矩形为正向矩形（即 width,height都大于等于0）
		 * 
		 */		
		protected function resetRectangle(border:Rectangle):void
		{
			if (border.width < 0)
			{
				border.x += border.width;
				border.width = -border.width;
			}
			if (border.height < 0)
			{
				border.y += border.height;
				border.height = -border.height;
			}
		}
	}
}
class AssistantInner
{
	
}